package com.nuaner.nfc.card;

import java.io.IOException;

import android.content.Context;
import android.nfc.tech.MifareClassic;
import android.util.Log;

public class MifareClassicCard {

	private static final byte[][] DEFAULT_KEYS = {
		{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff},
		{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00},
		{(byte)0xa0,(byte)0xa1,(byte)0xa2,(byte)0xa3,(byte)0xa4,(byte)0xa5},
		{(byte)0xb0,(byte)0xb1,(byte)0xb2,(byte)0xb3,(byte)0xb4,(byte)0xb5},
		{(byte)0x0a,(byte)0x1a,(byte)0x2a,(byte)0x3a,(byte)0x4a,(byte)0x5a},
		{(byte)0x4d,(byte)0x3a,(byte)0x99,(byte)0xc3,(byte)0x51,(byte)0xdd},
		{(byte)0x1a,(byte)0x98,(byte)0x2c,(byte)0x7e,(byte)0x45,(byte)0x9a},
		{(byte)0xd3,(byte)0xf7,(byte)0xd3,(byte)0xf7,(byte)0xd3,(byte)0xf7},
		{(byte)0xaa,(byte)0xbb,(byte)0xcc,(byte)0xdd,(byte)0xee,(byte)0xff},
		{(byte)0x00,(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x44,(byte)0x55},
		{(byte)0x71,(byte)0x4c,(byte)0x5c,(byte)0x88,(byte)0x6e,(byte)0x97},
		{(byte)0x58,(byte)0x7e,(byte)0xe5,(byte)0xf9,(byte)0x35,(byte)0x0f},
		{(byte)0xa0,(byte)0x47,(byte)0x8c,(byte)0xc3,(byte)0x90,(byte)0x91},
		{(byte)0x53,(byte)0x3c,(byte)0xb6,(byte)0xc7,(byte)0x23,(byte)0xf6},
		{(byte)0x8f,(byte)0xd0,(byte)0xa4,(byte)0xf2,(byte)0x56,(byte)0xe9}
	};
	
	private static final byte[] HEX_CHAR_TABLE = { 
		(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', 
		(byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' 
	};
	
	private int authedCount = 0;
	
	public StringBuilder load(Context context, MifareClassic tag) {
		StringBuilder data = new StringBuilder();
		try {
			tag.connect();
			
			data.append("CARD_ID: ");
			final byte[] ID;
			ID = tag.getTag().getId();
			if (ID == null) {
				throw new Exception();
			}
			data.append(changeToHexs(ID));
			
			int type, block, sector, size;
			data.append("\nTYPE: ");
			type = tag.getType();
			if (type == MifareClassic.TYPE_CLASSIC) {
				data.append("Mifare_Classic\n");
			} else if (type == MifareClassic.TYPE_PRO) {
				data .append("Mifare_Pro\n");
			} else if (type == MifareClassic.TYPE_PLUS) {
				data .append("Mifare_Plus\n");
			} else {
				data .append("TYPE_UNKNOW\n");
			}
			
			block = tag.getBlockCount();
			data.append( "TOTAL_BLOCK: " + block + "\n");
			sector = tag.getSectorCount();
			data.append( "TOTAL_SECTOR: " + sector + "\n");
			size = tag.getSize();
			data.append("SIZE:\n");
			if (size == MifareClassic.SIZE_MINI) {
				data.append( "MifareClassic_MINI\n");
			} else if (size == MifareClassic.SIZE_1K) {
				data.append( "MifareClassic_1K\n");
			} else if (size == MifareClassic.SIZE_2K) {
				data.append( "MifareClassic_2K\n");
			} else if (size == MifareClassic.SIZE_4K){
				data.append( "MifareClassic_4K\n");
			}
			
			for(int i = 0; i < tag.getSectorCount(); i++) {
				data.append( "Sector : " + i + "\n");
				int blocksInSector = tag.getBlockCountInSector(i);
				data.append( "Blocks in Sector: " + blocksInSector + "\n");
				try {
					data.append(readSectorWithA(tag, true, i));
				} catch (Exception e) {
					e.printStackTrace();
					try {
						data .append(readSectorWithA(tag, false, i));
					} catch(Exception e1) {
						e1.printStackTrace();
					}
				}
				data.append("\n");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			tag.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return data.append("\n\n");
	}
	
	private StringBuilder readSectorWithA(MifareClassic tag, boolean usingA, int sector) throws IOException {
		StringBuilder data = new StringBuilder();
		Boolean isAuthed = this.authendticateSectorWithKeysA(sector, DEFAULT_KEYS, tag, usingA);
		if(!isAuthed){
			data.append("Authentication " + ((usingA == true)?"A":"B") + " fail\n");
			throw new IOException(data + "in there");
		} else {
			data.append("Authentication " + ((usingA == true)?"A":"B") + " true\n");
		}
		int blocksInSector = tag.getBlockCountInSector(sector);
		int blockIndex = tag.sectorToBlock(sector);
		int num = 0;
		while(num++ < blocksInSector) {
			byte[] blockdata = tag.readBlock(blockIndex);
			data.append("Block : " + blockIndex++ + "\n");
			data.append(changeToHexs(blockdata));
			data.append("\n");
		}
		return data;
	}
	
	private char[] changeToChars(byte[] blockdata) {
		char[] tempdata = new char[blockdata.length * 2];
		int length = blockdata.length;
		for (int i = 0, j = 0; i < length; i++, j++) {
			int s = 0;
			if(blockdata[i] < 0)
				s += 256 + blockdata[i];
			else 
				s += blockdata[i];
			tempdata[j] = (char) s;
			if((i + 1) % 28 == 0) {
				tempdata[++j] = '\n';
			}
		}
		return tempdata;
	}

	private Boolean authendticateSectorWithKeysA(int i, byte[][] defaultKeys, MifareClassic tag, boolean isA) {
		int keys_num = defaultKeys.length;
		Boolean isAuthed = false;
		for(int num = 0; num < keys_num; num++) {
			try {
				if(isA == true) {
					isAuthed = tag.authenticateSectorWithKeyA(i, defaultKeys[num]);
					Log.d(tag.toString(), "in Authentication A " + isAuthed);
				} else {
					isAuthed = tag.authenticateSectorWithKeyB(i, defaultKeys[num]);
					Log.d(tag.toString(), "in Authentication B " + isAuthed);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			if(isAuthed == true) {
				authedCount ++;
				return true;
			}
		}
		return false;
	}
	
	private static String changeToHexs(byte[] blockdata) {
		byte[] hexs = new byte[blockdata.length * 3];
		for (int i = 0, j = 0; i < blockdata.length; i++, j += 2) {
			int head = blockdata[i] & 0xF0 >>> 4;
			hexs[j] = HEX_CHAR_TABLE[head];
			hexs[j+1] = HEX_CHAR_TABLE[blockdata[i] & 0x0F];
			if((i + 1) % 28 == 0) {
				hexs[j++] = '\n';
			}
		}
		return new String(hexs);
	}
}
